#ifdef LINUX
#include "Platform/LinuxPlatform.h"
#include <iostream>


#include "X11Window.h"
#include <X11/Xatom.h>
#include <vulkan/vulkan_xlib.h>

#include "Platform/X11Things.h"



CX11Window::CX11Window()
{

    
    auto _Display = GetDisplay();
    int Screen = DefaultScreen(_Display);

    Window _Window = 
        XCreateSimpleWindow(_Display
            , RootWindow(_Display, Screen)
            , 0
            , 0
            , 800
            , 600
            , 1, BlackPixel(_Display, Screen), WhitePixel(_Display, Screen));
    WindowID = _Window;
    
    XSelectInput(_Display, _Window,            
            ExposureMask 
            | KeyPressMask 
            | KeyReleaseMask 
            | ButtonPressMask 
            | ButtonReleaseMask 
            | PointerMotionMask
            | EnterWindowMask 
            | LeaveWindowMask
            | StructureNotifyMask  // for resize event
            | FocusChangeMask // for window get/lose focus event
            );


    XStoreName(_Display, _Window, "NoNameWindow");

    //enable drag and drop
	Atom XdndAware = XInternAtom(_Display, "XdndAware", False);
	//Atom XA_ATOM = XInternAtom(_Display, "XA_ATOM", False);
    XChangeProperty(_Display, _Window, XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*)&XdndAware, 1);


    //send delte window event when close button is pressed
    Atom DeleteWindow = XInternAtom(_Display, "WM_DELETE_WINDOW", 0);
    XSetWMProtocols(_Display, _Window, &DeleteWindow, 1);


}

CX11Window::~CX11Window()
{
    // if(CaptureMouseWindow==WindowID)
    // {
    //     CaptureMouseWindow=0;
    // }
    XDestroyWindow(GetDisplay(), (Window)WindowID);
}

uint64_t CX11Window::GetWindowID() const
{
    return WindowID;
}

SIntPoint CX11Window::GetContentPosition() const
{
    //get the window content position, do not consider boarder and title bar
    Window Child;
    int x, y;
    XTranslateCoordinates(GetDisplay(), (Window)WindowID, RootWindow(GetDisplay(), DefaultScreen(GetDisplay())), 0, 0, &x, &y, &Child);
    return SIntPoint(x, y);
}

SIntPoint CX11Window::GetContentSize() const
{
    //get the window content size, do not consider boarder and title bar
    XWindowAttributes Attr;
    XGetWindowAttributes(GetDisplay(), (Window)WindowID, &Attr);
    return SIntPoint(Attr.width, Attr.height);
}

SIntPoint CX11Window::GetWindowSize() const
{
    //get the window size, include boarder and title bar
    XWindowAttributes Attr;
    XGetWindowAttributes(GetDisplay(), (Window)WindowID, &Attr);
    return SIntPoint(Attr.width, Attr.height);
}

void CX11Window::SetWindowSize(SIntPoint InSize)
{
    if(InSize==GetWindowSize())
    {
        //no need to change window size
        return;
    }

    if(InSize.X==0 || InSize.Y==0)
    {
        //cant set window size to 0, so hide window
        XUnmapWindow(GetDisplay(), (Window)WindowID);
    }
    else
    {
        //if window is not mapped, map it first

        XMapWindow(GetDisplay(), (Window)WindowID);
        
        //set window size
        XFlush(GetDisplay());
        Display *Display = GetDisplay();
        XResizeWindow(Display, (Window)WindowID, InSize.X, InSize.Y);
    }

}

SIntPoint CX11Window::GetWindowPosition() const
{
    //get the window position in screen
    Window Child;
    int x, y;
    XTranslateCoordinates(GetDisplay(), (Window)WindowID, RootWindow(GetDisplay(), DefaultScreen(GetDisplay())), 0, 0, &x, &y, &Child);
    return SIntPoint(x, y);
}

void CX11Window::SetWindowPosition(SIntPoint InPosition)
{
    XMoveWindow(GetDisplay(), (Window)WindowID, InPosition.X, InPosition.Y);
}

void CX11Window::MoveToScreenCenter()
{
    SRect ScreenArea = GetScreenPlacableArea();
    SIntPoint WindowSize = GetWindowSize();
    SetWindowPosition(SIntPoint(
        (ScreenArea.Extent.X-WindowSize.X)/2+ScreenArea.Min.X,
        (ScreenArea.Extent.Y-WindowSize.Y)/2+ScreenArea.Min.Y
    ));

}

void CX11Window::SetWindowTitle(const CString &InTitle)
{
    auto Title8 = InTitle.ToUtf8();
    XStoreName(GetDisplay(), (Window)WindowID, (const char*)Title8.Data() );
}

SRect CX11Window::GetScreenPlacableArea()
{
    //query screen work area
    Atom WorkAreaAtom = XInternAtom(GetDisplay(), "_NET_WORKAREA", False);

    Atom Type;
    int Format;
    unsigned long Count, Remaining;
    unsigned char *Data;

    XGetWindowProperty(GetDisplay(), DefaultRootWindow(GetDisplay()), WorkAreaAtom, 0, 1024, False, AnyPropertyType, &Type, &Format, &Count, &Remaining, &Data);

    long *WorkArea = (long*)Data;
    return SRect(WorkArea[0], WorkArea[1], WorkArea[2], WorkArea[3]);
}

bool CX11Window::IsMaximized() const
{
    // query window state
    Atom StateAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE", False);
    Atom MaximizedHorzAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE_MAXIMIZED_HORZ", False);
    Atom MaximizedVertAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE_MAXIMIZED_VERT", False);

    Atom Type;
    int Format;
    unsigned long Count, Remaining;
    unsigned char *Data;

    XGetWindowProperty(GetDisplay(), (Window)WindowID, StateAtom, 0, 1024, False, AnyPropertyType, &Type, &Format, &Count, &Remaining, &Data);

    Atom *Atoms = (Atom*)Data;
    for(unsigned long i = 0; i < Count; i++)
    {
        if(Atoms[i] == MaximizedHorzAtom || Atoms[i] == MaximizedVertAtom)
        {
            return true;
        }
    }

    return false;
}

void CX11Window::Maximize()
{
    // set window state
    Atom StateAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE", False);
    Atom MaximizedHorzAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE_MAXIMIZED_HORZ", False);
    Atom MaximizedVertAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE_MAXIMIZED_VERT", False);

    XEvent Event;
    memset(&Event, 0, sizeof(Event));
    Event.type = ClientMessage;
    Event.xclient.window = (Window)WindowID;
    Event.xclient.message_type = StateAtom;
    Event.xclient.format = 32;
    Event.xclient.data.l[0] = 1;
    Event.xclient.data.l[1] = MaximizedHorzAtom;
    Event.xclient.data.l[2] = MaximizedVertAtom;
    Event.xclient.data.l[3] = 1;
    Event.xclient.data.l[4] = 0;

    XSendEvent(GetDisplay(), DefaultRootWindow(GetDisplay()), False, SubstructureRedirectMask | SubstructureNotifyMask, &Event);

}

bool CX11Window::IsNormalSizeAndPosition() const
{
    return  !IsMinimized() && !IsMaximized() && !IsFullScreen();
}

void CX11Window::Restore()
{
    if(IsMinimized())
    {
        RestoreFromMinimized();
    }
    else if(IsMaximized())
    {
        RestoreFromMaximized();
    }
    else if(IsFullScreen())
    {
        RestoreFromFullScreen();
    }
}

bool CX11Window::IsMinimized() const
{
    // query window state
    Atom StateAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE", False);
    Atom MinimizedAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE_HIDDEN", False);

    Atom Type;
    int Format;
    unsigned long Count, Remaining;
    unsigned char *Data;

    XGetWindowProperty(GetDisplay(), (Window)WindowID, StateAtom, 0, 1024, False, AnyPropertyType, &Type, &Format, &Count, &Remaining, &Data);

    Atom *Atoms = (Atom*)Data;
    for(unsigned long i = 0; i < Count; i++)
    {
        if(Atoms[i] == MinimizedAtom)
        {
            return true;
        }
    }

    return false;

}

void CX11Window::Minimize()
{
    XIconifyWindow(GetDisplay(), (Window)WindowID, DefaultScreen(GetDisplay()));

    //q: what message sent by XIconifyWindow ?
    //a: XIconifyWindow sends a ClientMessage event with the message type WM_CHANGE_STATE and the data.l[0] field set to IconicState.
}

bool CX11Window::IsFullScreen() const
{
    // query window state
    Atom StateAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE", False);
    Atom FullScreenAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE_FULLSCREEN", False);

    Atom Type;
    int Format;
    unsigned long Count, Remaining;
    unsigned char *Data;

    XGetWindowProperty(GetDisplay(), (Window)WindowID, StateAtom, 0, 1024, False, AnyPropertyType, &Type, &Format, &Count, &Remaining, &Data);

    Atom *Atoms = (Atom*)Data;
    for(unsigned long i = 0; i < Count; i++)
    {
        if(Atoms[i] == FullScreenAtom)
        {
            return true;
        }
    }

    return false;
}

void CX11Window::FullScreen()
{
    // set window state
    Atom StateAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE", False);
    Atom FullScreenAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE_FULLSCREEN", False);

    XEvent Event;
    Event.type = ClientMessage;
    Event.xclient.window = (Window)WindowID;
    Event.xclient.message_type = StateAtom;
    Event.xclient.format = 32;
    Event.xclient.data.l[0] = 1;
    Event.xclient.data.l[1] = FullScreenAtom;
    Event.xclient.data.l[2] = 0;
    Event.xclient.data.l[3] = 0;
    Event.xclient.data.l[4] = 0;

    XSendEvent(GetDisplay(), DefaultRootWindow(GetDisplay()), False, SubstructureRedirectMask | SubstructureNotifyMask, &Event);

}

void CX11Window::ShowBorder()
{
   //show window border
    Atom WM_HINTS = XInternAtom(GetDisplay(), "_MOTIF_WM_HINTS", False);
    struct
    {
        unsigned long flags;
        unsigned long functions;
        unsigned long decorations;
        long input_mode;
        unsigned long status;
    } MWMHints = {2, 0, 1, 0, 0};
    XChangeProperty(GetDisplay(), (Window)WindowID, WM_HINTS, WM_HINTS, 32, PropModeReplace, (unsigned char*)&MWMHints, 5);
}

void CX11Window::HideBorder()
{

    //make window borderless
    Atom WM_HINTS = XInternAtom(GetDisplay(), "_MOTIF_WM_HINTS", False);
    struct
    {
        unsigned long flags;
        unsigned long functions;
        unsigned long decorations;
        long input_mode;
        unsigned long status;
    } MWMHints = {2, 0, 0, 0, 0};
    XChangeProperty(GetDisplay(), (Window)WindowID, WM_HINTS, WM_HINTS, 32, PropModeReplace, (unsigned char*)&MWMHints, 5);

}

bool CX11Window::IsBorderVisible() const
{
    //check if window has border
    Atom WM_HINTS = XInternAtom(GetDisplay(), "_MOTIF_WM_HINTS", False);
    Atom Type;
    int Format;
    unsigned long Count, Remaining;
    unsigned char *Data;

    XGetWindowProperty(GetDisplay(), (Window)WindowID, WM_HINTS, 0, 1024, False, AnyPropertyType, &Type, &Format, &Count, &Remaining, &Data);

    if(Count==0)
    {
        return true;
    }

    unsigned long *Hints = (unsigned long*)Data;
    return (Hints[2] & 1) != 0;
}

// Window CX11Window::CaptureMouseWindow = 0;

// void CX11Window::StartCaptureMouse()
// {
//     if(CaptureMouseWindow != 0)
//     {
//         return;
//     }

//     CaptureMouseWindow = (Window)WindowID;
// }

// void CX11Window::StopCaptureMouse()
// {
//     if(CaptureMouseWindow != (Window)WindowID)
//     {
//         return;
//     }

//     CaptureMouseWindow = 0;
// }

vk::SurfaceKHR CX11Window::CreateVkSurfaceKHR(vk::Instance Instance)
{
    VkXlibSurfaceCreateInfoKHR SurfaceCreateInfo = {};
    SurfaceCreateInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
    SurfaceCreateInfo.dpy = GetDisplay();
    SurfaceCreateInfo.window = (Window)WindowID;


    VkSurfaceKHR Surface;
    vkCreateXlibSurfaceKHR(Instance, &SurfaceCreateInfo, nullptr, &Surface);

    XFlush(GetDisplay());

    return Surface;
}

void CX11Window::Show()
{
    XMapWindow(GetDisplay(), (Window)WindowID);
}

void CX11Window::Hide()
{
    XUnmapWindow(GetDisplay(), (Window)WindowID);

}

bool CX11Window::IsVisible() const
{
    XWindowAttributes Attr;
    XGetWindowAttributes(GetDisplay(), (Window)WindowID, &Attr);
    return Attr.map_state == IsViewable;
}

void CX11Window::SetOpcity(float InOpacity)
{
    if(InOpacity==1.0f)
    {
        XDeleteProperty(GetDisplay(), (Window)WindowID, XInternAtom(GetDisplay(), "_NET_WM_WINDOW_OPACITY", False));
        return;

    }

    // set window opacity
    Atom OpacityAtom = XInternAtom(GetDisplay(), "_NET_WM_WINDOW_OPACITY", False);
    unsigned long Opacity = (unsigned long)(InOpacity * 0xffffffff);

    XChangeProperty(GetDisplay(), (Window)WindowID, OpacityAtom, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&Opacity, 1);
}

void CX11Window::EnableThisWindow(bool bEnable)
{
    //enable/disable input event
    XSelectInput(GetDisplay(), (Window)WindowID, bEnable ? 
        ExposureMask 
        | KeyPressMask 
        | KeyReleaseMask 
        | ButtonPressMask 
        | ButtonReleaseMask 
        | PointerMotionMask
        | EnterWindowMask 
        | LeaveWindowMask
        | StructureNotifyMask  // for resize event
        : 0);
}

bool CX11Window::IsThisWindowEnabled() const
{
    //check if input event is enabled
    unsigned long EventMask;
    XWindowAttributes Attr;
    XGetWindowAttributes(GetDisplay(), (Window)WindowID, &Attr);
    EventMask = Attr.your_event_mask;
    return EventMask != 0;
}

void CX11Window::BringToFront()
{
    XRaiseWindow(GetDisplay(), (Window)WindowID);
}

void CX11Window::HideInTaskbar()
{
    Atom SkipTaskbarAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE_SKIP_TASKBAR", False);
    Atom StateAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE", False);

    XEvent Event;
    Event.type = ClientMessage;
    Event.xclient.window = (Window)WindowID;
    Event.xclient.message_type = StateAtom;
    Event.xclient.format = 32;
    Event.xclient.data.l[0] = 1;
    Event.xclient.data.l[1] = SkipTaskbarAtom;
    Event.xclient.data.l[2] = 0;
    Event.xclient.data.l[3] = 0;
    Event.xclient.data.l[4] = 0;

    XSendEvent(GetDisplay(), DefaultRootWindow(GetDisplay()), False, SubstructureRedirectMask | SubstructureNotifyMask, &Event);
}

Display *CX11Window::GetDisplay() const
{
   return ((CLinuxPlatform*)IPlatformInterface::GetPlatformInterface())->GetX11Things()->_Display;
}

void CX11Window::RestoreFromMaximized()
{
     // set window state
    Atom StateAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE", False);
    Atom MaximizedHorzAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE_MAXIMIZED_HORZ", False);
    Atom MaximizedVertAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE_MAXIMIZED_VERT", False);

    XEvent Event;
    memset(&Event, 0, sizeof(Event));
    Event.type = ClientMessage;
    Event.xclient.window = (Window)WindowID;
    Event.xclient.message_type = StateAtom;
    Event.xclient.format = 32;
    Event.xclient.data.l[0] = 0;
    Event.xclient.data.l[1] = MaximizedHorzAtom;
    Event.xclient.data.l[2] = MaximizedVertAtom;
    Event.xclient.data.l[3] = 1;

    XSendEvent(GetDisplay(), DefaultRootWindow(GetDisplay()), False, SubstructureRedirectMask | SubstructureNotifyMask, &Event);
}

void CX11Window::RestoreFromMinimized()
{
    XMapWindow(GetDisplay(), (Window)WindowID);
    // // set window state
    // Atom StateAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE", False);
    // Atom MinimizedAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE_HIDDEN", False);

    // XEvent Event;
    // Event.type = ClientMessage;
    // Event.xclient.window = (Window)WindowID;
    // Event.xclient.message_type = StateAtom;
    // Event.xclient.format = 32;
    // Event.xclient.data.l[0] = 0;
    // Event.xclient.data.l[1] = MinimizedAtom;
    // Event.xclient.data.l[2] = 0;
    // Event.xclient.data.l[3] = 0;
    // Event.xclient.data.l[4] = 0;

    // XSendEvent(GetDisplay(), DefaultRootWindow(GetDisplay()), False, SubstructureRedirectMask | SubstructureNotifyMask, &Event);
   
}

void CX11Window::RestoreFromFullScreen()
{
    // set window state
    Atom StateAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE", False);
    Atom FullScreenAtom = XInternAtom(GetDisplay(), "_NET_WM_STATE_FULLSCREEN", False);

    XEvent Event;
    Event.type = ClientMessage;
    Event.xclient.window = (Window)WindowID;
    Event.xclient.message_type = StateAtom;
    Event.xclient.format = 32;
    Event.xclient.data.l[0] = 0;
    Event.xclient.data.l[1] = FullScreenAtom;
    Event.xclient.data.l[2] = 0;
    Event.xclient.data.l[3] = 0;
    Event.xclient.data.l[4] = 0;

    XSendEvent(GetDisplay(), DefaultRootWindow(GetDisplay()), False, SubstructureRedirectMask | SubstructureNotifyMask, &Event);
}

#endif